package cn.iocoder.yudao.framework.common.util.ipa;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @author liujq
 * @date 20250110 16:40
 */
@Slf4j
public class IpaDecompressionUtil {

    @Data
    public static class ExtractResult {
        private boolean success;
        private String message;
        private String extractPath;
        private String payloadPath;
        private String appPath;

        public static ExtractResult success(String extractPath, String payloadPath, String appPath) {
            ExtractResult result = new ExtractResult();
            result.setSuccess(true);
            result.setExtractPath(extractPath);
            result.setPayloadPath(payloadPath);
            result.setAppPath(appPath);
            return result;
        }

        public static ExtractResult fail(String message) {
            ExtractResult result = new ExtractResult();
            result.setSuccess(false);
            result.setMessage(message);
            return result;
        }
    }

    /**
     * 解压 IPA 文件
     *
     * @param ipaFile IPA 文件
     * @param destDir 目标目录
     * @return 解压结果
     */
    public static ExtractResult extractIpa(File ipaFile, String destDir) {
        try {
            // 1. 参数校验
            if (!ipaFile.exists() || !ipaFile.isFile()) {
                return ExtractResult.fail("IPA file does not exist or is not a file");
            }
            if (!ipaFile.getName().toLowerCase().endsWith(".ipa")) {
                return ExtractResult.fail("File is not an IPA file");
            }

            // 2. 检查并创建目标目录
            File destDirectory = new File(destDir);
            if (!destDirectory.exists() && !destDirectory.mkdirs()) {
                return ExtractResult.fail("Failed to create destination directory");
            }

            // 3. 检查磁盘空间
            if (!checkDiskSpace(ipaFile, destDirectory)) {
                return ExtractResult.fail("Insufficient disk space");
            }

            // 4. 解压文件
            String extractPath = unzipIPA(ipaFile, destDirectory);
            if (extractPath == null) {
                return ExtractResult.fail("Failed to extract IPA file");
            }

            // 5. 查找 Payload 目录
            String payloadPath = findPayloadDirectory(extractPath);
            if (payloadPath == null) {
                return ExtractResult.fail("Payload directory not found");
            }

            // 6. 查找 .app 目录
            String appPath = findAppDirectory(payloadPath);
            if (appPath == null) {
                return ExtractResult.fail(".app directory not found");
            }

            return ExtractResult.success(extractPath, payloadPath, appPath);

        } catch (Exception e) {
            log.error("Failed to extract IPA file", e);
            return ExtractResult.fail("Error extracting IPA: " + e.getMessage());
        }
    }

    private static String unzipIPA(File ipaFile, File destDir) throws IOException {
        // 如果没有指定目标目录，则使用临时目录
        String extractPath=destDir.getAbsolutePath();

        File extractDir = new File(extractPath);
        if (!extractDir.exists() && !extractDir.mkdirs()) {
            throw new IOException("Failed to create extraction directory: " + extractPath);
        }

        log.info("Extracting IPA to: {}", extractPath);

        try (ZipFile zipFile = new ZipFile(ipaFile)) {
            Enumeration<? extends ZipEntry> entries = zipFile.entries();

            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                String entryName = entry.getName();

                // 跳过 Mac 系统文件
                if (shouldSkipEntry(entryName)) {
                    continue;
                }

                // 处理文件路径
                String fileName = normalizePath(entryName);
                File targetFile = new File(extractDir, fileName);

                // 安全检查
                if (!isPathSafe(targetFile, extractPath)) {
                    log.warn("Potentially malicious entry detected: {}", entryName);
                    continue;
                }

                if (entry.isDirectory()) {
                    if (!targetFile.exists() && !targetFile.mkdirs()) {
                        throw new IOException("Failed to create directory: " + targetFile);
                    }
                } else {
                    // 确保父目录存在
                    File parent = targetFile.getParentFile();
                    if (!parent.exists() && !parent.mkdirs()) {
                        throw new IOException("Failed to create directory: " + parent);
                    }

                    // 复制文件内容
                    try (InputStream is = zipFile.getInputStream(entry);
                         OutputStream os = new FileOutputStream(targetFile)) {
                        IOUtils.copy(is, os);
                    }

                    // 设置文件权限
                    setFilePermissions(targetFile);
                }
            }
        }

        return extractPath;
    }

    private static boolean shouldSkipEntry(String entryName) {
        return entryName.contains("__MACOSX") ||
                entryName.contains(".DS_Store") ||
                entryName.startsWith(".");
    }

    private static String normalizePath(String path) {
        return path.replace('\\', '/');
    }

    private static boolean isPathSafe(File file, String basePath) {
        try {
            String canonicalPath = file.getCanonicalPath();
            String canonicalBasePath = new File(basePath).getCanonicalPath();
            return canonicalPath.startsWith(canonicalBasePath);
        } catch (IOException e) {
            return false;
        }
    }

    private static void setFilePermissions(File file) {
        // 设置基本权限
        file.setReadable(true, false);
        file.setWritable(true, true);

        // 如果是目录，设置执行权限
        if (file.isDirectory()) {
            file.setExecutable(true, false);
        }

        // 在 Linux/Mac 系统上设置额外权限
        if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
            try {
                Path path = file.toPath();
                if (file.isDirectory()) {
                    Files.setPosixFilePermissions(path, PosixFilePermissions.fromString("rwxr-xr-x"));
                } else {
                    Files.setPosixFilePermissions(path, PosixFilePermissions.fromString("rw-r--r--"));
                }
            } catch (Exception e) {
                log.warn("Failed to set POSIX permissions for: {}", file.getPath());
            }
        }
    }

    private static boolean checkDiskSpace(File sourceFile, File destDir) {
        long requiredSpace = sourceFile.length() * 2; // 预估解压后的大小
        long usableSpace = destDir.getUsableSpace();
        return usableSpace > requiredSpace;
    }

    private static String findPayloadDirectory(String extractPath) {
        File payloadDir = new File(extractPath, "Payload");
        return payloadDir.exists() && payloadDir.isDirectory() ? payloadDir.getAbsolutePath() : null;
    }

    private static String findAppDirectory(String payloadPath) {
        File payloadDir = new File(payloadPath);
        File[] files = payloadDir.listFiles((dir, name) -> name.endsWith(".app"));
        return files != null && files.length > 0 ? files[0].getAbsolutePath() : null;
    }
}
